# coding=utf-8

from copy import deepcopy
from random import shuffle, sample
import re


class Question:
    def __init__(self, desc = "", options = None, correct_index = 0):
        self.desc = desc
        self.options = options
        self.correct_index = correct_index

    
    def QuestionID(self):
        vals = (self.desc, self.options[self.correct_index])
        return hash(vals)
    

    def SetDescription(self, desc):
        assert desc
        self.desc = desc
    

    def GetDescription(self):
        return self.desc
    

    def GetOptionCount(self):
        return 4
    

    def SetOptions(self, options):
        assert isinstance(options, list)
        assert len(options) == self.GetOptionCount()
        self.options = deepcopy(options)


    def SetCorrectIndex(self, index):
        self.correct_index = index


    def GetOption(self, index):
        assert index >=0 and index < self.GetOptionCount()
        return self.options[index]
    

    def GetCorrectOption(self):
        return self.options[self.correct_index]


    def Judge(self, index):
        return self.correct_index == index
    

    def Randomize(self):
        m = [0, 1, 2, 3]
        newOptions = []

        shuffle(m)

        # newOptions[m[0]] = self.options[0]
        # newOptions[m[1]] = self.options[1]
        # newOptions[m[2]] = self.options[2]
        # newOptions[m[3]] = self.options[3]

        # [0, 1, 2, 3] # old
        # [2, 1, 0, 3] # new

        old_correct_answer = self.GetCorrectOption()
        # print("correct_index: ", self.correct_index)

        # found_index = False
        new_correct_index = None#-1
        for i in range(4):
            newOptions.append(self.options[m[i]])
            # print("i:", i)
            if m[i] == self.correct_index:
                # print("m[i]: ", m[i], ", ci: ", self.correct_index)
                new_correct_index = i
                # found_index = True
        self.correct_index = new_correct_index
        
        self.options = newOptions
        new_correct_answer = self.GetCorrectOption()
        assert old_correct_answer == new_correct_answer


class MatchQuestion(Question):
    def __init__(self, question, answer, reverse = False):
        if reverse:
            question, answer = answer, question
        self.SetDescription(question)
        options = [answer, None, None, None]
        self.SetOptions(options)
        self.SetCorrectIndex(0)
        # self.reverse(

        # pass


class QuestionSet:
    def __init__(self, summary):
        self.summary = summary
        self.questions = []


    def AddQuestion(self, question):
        self.questions.append(question)


    def GetQuestions(self):
        return self.questions


    def GetQuestionCount(self):
        return len(self.questions)


    def Randomize(self):
        assert False


class MatchQuestionSet(QuestionSet):
    def __init__(self, summary):
        super().__init__(summary)
        pass


    def Randomize(self):
        assert len(self.questions) >= 4
        answers = {x.GetCorrectOption() for x in self.questions}

        for ques in self.questions:
            correct_answer = ques.GetCorrectOption()
            all_wrong_ansers = answers - {correct_answer}
            wrong_answers = sample(sorted(all_wrong_ansers), 3)
            
            assert len(wrong_answers) == ques.GetOptionCount() - 1
            wrong_index = 0
            for i in range(ques.GetOptionCount()):
                if i != ques.correct_index:
                    ques.options[i] = wrong_answers[wrong_index]
                    wrong_index += 1

            assert wrong_index == len(wrong_answers)
            ques.Randomize()


class QuestionGroup:
    def __init__(self, summary):
        self.summary = summary
        self.question_sets = []
        self.question_subgroups = []


    def AddQuestionSet(self, ques_set):
        self.question_sets.append(ques_set)


    def AddQuestionSubGroup(self, ques_group):
        self.question_subgroups.append(ques_group)

    
    def GetQuestions(self, root = 0):
        ques = []
        for ques_set in self.question_sets:
            ques += ques_set.GetQuestions()

        for ques_group in self.question_subgroups:
            ques += ques_group.GetQuestions(root + 1)

        if root == 0:
            shuffle(ques)

        return ques


    def GetQuestionCount(self):
        count = 0
        for ques_set in self.question_sets:
            count += ques_set.GetQuestionCount()

        for ques_group in self.question_subgroups:
            count += ques_group.GetQuestionCount()

        return count


def showQuestions(questions):
    if isinstance(questions, list):
        for ques in questions:
            print("hash: ", ques.QuestionID())
            print("desc:", ques.GetDescription())
            for i in range(4):
                print("option %d:" % i, ques.GetOption(i))
            print("correct: ", ques.GetCorrectOption())
            print('-' * 16)
    elif isinstance(questions, (QuestionSet, QuestionGroup)):
        print("+++ Summary: %s +++" % questions.summary)
        print("+++ Total: %d +++" % questions.GetQuestionCount())
        print("+" * 16)
        showQuestions(questions.GetQuestions())


def createMatchQuestionSet(summary, questions, reverse = False):
    matchSet = MatchQuestionSet(summary)
    for q, a in questions:
        matchQues = MatchQuestion(q, a, reverse)
        matchSet.AddQuestion(matchQues)

    assert len(matchSet.GetQuestions()) >= 4
    matchSet.Randomize()
    return matchSet


def createMatchQuestionGroup(summary, questions, reverse = False):
    grp = QuestionGroup(summary)
    st = createMatchQuestionSet(summary, questions, reverse)
    grp.AddQuestionSet(st)
    return grp


def createFillInBlankQuestionSet(summary, questions):
    def generate_fill_blank_questions(input_str):

        def remove_brackets(s):
            arr = [x for x in s if x not in ('(', ')')]
            return ''.join(arr)

        rs = []
        matches = re.finditer(r'\((.*?)\)', input_str)
        for match in matches:
            start, end = match.span()

            str_left = input_str[:start]
            str_right = input_str[end:]
            key = match.group(1)
            ques = remove_brackets(str_left) + "__" * len(key) + remove_brackets(str_right)

            rs.append((ques, key))
        
        return rs

    rs = []
    for ques in questions:
        rs += generate_fill_blank_questions(ques)

    matchSet = MatchQuestionSet(summary)
    for q, a in rs:
        matchQues = MatchQuestion(q, a)
        matchSet.AddQuestion(matchQues)

    assert len(matchSet.GetQuestions()) >= 4
    matchSet.Randomize()
    return matchSet


def createFillInBlankQuestionGroup(summary, questions):
    grp = QuestionGroup(summary)
    st = createFillInBlankQuestionSet(summary, questions)
    grp.AddQuestionSet(st)
    return grp


def test():
    questions = [
        ("AAA", "aaa"),
        ("BBB", "bbb"),
        ("CCC", "ccc"),
        ("DDD", "ddd"),
        ("EEE", "eee"),
    ]

    matchSet = createMatchQuestionSet("Upper to Lower", questions)

    questions = matchSet.GetQuestions()
    print("###", matchSet.summary)
    showQuestions(questions)

    group1 = QuestionGroup("Group1")
    group1.AddQuestionSet(matchSet)

    print("###", group1.summary)
    questions = group1.GetQuestions()
    showQuestions(questions)

    questions = [
        ("AAA1", "aaa2"),
        ("BBB1", "bbb2"),
        ("CCC1", "ccc2"),
        ("DDD1", "ddd2"),
        ("EEE1", "eee2"),
    ]

    matchSet2 = createMatchQuestionSet("Upper2 to Lower2", questions)

    group2 = QuestionGroup("Group2")
    group2.AddQuestionSet(matchSet2)
    group2.AddQuestionSubGroup(group1)

    print("###", group2.summary)
    questions = group2.GetQuestions()
    print("question count: ", len(questions))
    showQuestions(questions)

    return group2


if __name__ == '__main__':
    test()
